Repository navigation
fix(cli): tools preference and catalog exit with a message on bad input or output - #239
Conversation
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
… be written Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
… written Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
….json is not UTF-8 or unreadable Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
What changed in this PR
Improves CLI error handling for tools preference, catalog trigger-set, and catalog scan by turning file read/write OS + decode failures into clean one-line messages with a non-zero exit, and adds regression tests for these cases.
Changes:
- Add guarded read/write paths for groups files, manifests, and
--out/--items-outoutputs with clear error messages. - Refactor
catalog trigger-setoutput writing into a helper that handles unwritable destinations. - Add/extend tests covering non-UTF-8 inputs and unwritable output paths; update complexity baseline.
| File | Description |
|---|---|
| dokimi/tests/test_tools_preference_items_out.py | New tests for tools preference --items-out unwritable path behavior. |
| dokimi/tests/test_tool_preference.py | Adds coverage for non-UTF-8 --groups input in tools preference. |
| dokimi/tests/test_skill_selection.py | Adds coverage for catalog trigger-set unwritable --out. |
| dokimi/tests/test_catalog_command.py | Adds coverage for non-UTF-8/unreadable manifests in catalog scan. |
| dokimi/scripts/growth_baseline.json | Updates complexity budget for the refactor in cmd_catalog_trigger_set. |
| dokimi/dokimi/external/catalog.py | Converts manifest read/decode errors into user-friendly ValueErrors. |
| dokimi/dokimi/commands/tools.py | Adds _write_items and improves --groups error reporting. |
| dokimi/dokimi/commands/catalog.py | Adds _write_trigger_set with clean error reporting on write failures. |
| dokimi/changelog.d/20261025_120000_vchamarthi_cli-input-tools-catalog.md | Changelog entry describing the improved CLI error behavior. |
Review effort: Lite
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #239 +/- ##
==========================================
+ Coverage 92.76% 92.78% +0.02%
==========================================
Files 256 256
Lines 32414 32498 +84
Branches 5421 5426 +5
==========================================
+ Hits 30068 30153 +85
+ Misses 1561 1559 -2
- Partials 785 786 +1
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 192057b. The fix does what it says for the cases it names: non-UTF-8 --groups, unwritable --items-out and --out, unreadable or non-UTF-8 manifests all exit with a one-line message. The problems are on the paths right next to them, which still end in a traceback, and in one test that does not pin the widened except. I'd fix these before merge.
What I ran, on a Linux review host at 192057b:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). The changed-line coverage check passed.- Reproductions for the code findings, offline.
- Mutation runs: with
external/catalog.pyorcommands/catalog.pyreverted to the base, the new catalog tests fail (4 and 3 tests). Seven of eight mutations of thetoolscode and tests are caught; the survivor is the first inline test comment ontest_tool_preference.py.
Not anchored inline:
tools preference --out-jsonis still unguarded, and it is written after all the model calls.tools.py:139callssave_artifact("tool_preference", data, args.out_json). With--out-json blocker/o.json, whereblockeris a regular file, the command ends in aFileExistsErrortraceback after the paid calls have run.--items-outis now checked before any spend, so this is the remaining unguarded output. This predates the PR, but the PR title and changelog saytools preferenceexits with a message on an unwritable output. Suggestion: guard it the same way as_write_items, or check the parent of--out-jsonbefore the calls.
Checked, no issues
except ValueErrorintools.py: Copilot's point on the broadValueErrordoes not apply:tp.GroupsErroris aValueErrorsubclass, and I found no input that makesload_groupsraise anotherValueError(wrong types for ids, names and vendors all becomeGroupsError). The only otherValueErrorin the module is raised frombuild_items, outside thetry.- Callers of the manifest readers:
read_skill_manifestandread_task_manifestare called only from the two scan helpers, and both catchValueError. --outfailures: a directory, a file as parent, a symlink loop and a dangling symlink all give the one-line message forreachandskill-creator, and forquestionsapart from the empty path below.- Repo rules: the changelog fragment passes the fragment check, and the growth baseline entry (16 to 15) is correct per
check_growth.py. No schema or docs pages are touched.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 8940627, which fix the two nits from my review:
766eae4:dokimi/dokimi/commands/catalog.py:140: the docstring now ends "when it cannot be written."8940627:dokimi/tests/test_skill_selection.py:485: thematch=pattern wraps the path inre.escape(str(out));import rewas already there.
Left as is:
dokimi/dokimi/commands/tools.py:139: the unguarded--out-jsonis not labelledNit:orNon-blocking:, and guarding it would change behaviour, so it stays with the author.
make ci-local at 8940627: 29 passed, 0 failed, 7 skipped.
Not approving yet. Still open: the five risks from my review.
|
@exai-bot /review |
|
@vchamarthi I did not start this: 4 requests are already waiting for a worker. Ask again in a few minutes. |
…ut-json and empty --out read_task_manifest raises ValueError naming suites.json when the top level, suites or a suite's tasks have the wrong type. tools preference --groups rejects a non-object document and catches only JSON, UTF-8, OS and groups errors. --out-json and an empty trigger-set --out exit 1 with a message instead of a traceback. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
|
@exai-bot /review |
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 0f9bfd9e68a6, requested by @vchamarthi, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 7 of 93 turns, 0.4 of 48 minutes, 141k in / 2k out tokens.
The change wraps the reads and writes of tools preference, catalog trigger-set and catalog scan so OS and decode errors exit with a one-line message. It also validates the shape of suites.json and of the groups document. The changes look correct and the tests cover the new paths. I found one minor problem with the message wording. I could not run the tests here because pytest isn't installed in the container.
1 finding is on the changed lines, as comments below.
Not verified
I did not run the new tests. I also did not check that make ci-local, including the growth baseline drop from 16 to 15 for cmd_catalog_trigger_set, is green.
…le --out ArtifactValidationError subclasses ValueError, so a schema failure was reported as 'cannot write --out'. It now exits with 'questions set rejected', and the write-failure message stays for path errors. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 5d1c627fe3d8, requested by @vchamarthi, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 7 of 93 turns, 0.5 of 48 minutes, 121k in / 3k out tokens.
Re-review of 5d1c627fe3d8: 1 new finding.
1 finding is on the changed lines, as comments below.
napetrov
left a comment
There was a problem hiding this comment.
Review at 5d1c627f.
Summary. All earlier findings (antonwolfy, Copilot, exai-bot round 1) are addressed in 0f9bfd9 / 766eae4 / 8940627 / 5d1c627: wrong-shape suites.json and --groups, the narrowed except with a test that pins it, --out "", and the schema-rejection message. I found nothing new on the changed lines and pushed no commits.
Open items
- Minor, design choice for the author (not a small fix, so left as is):
tools preferenceonly discovers an unwritable--out-jsonin_save_result, after the paid probe run, so the results are lost (exai-bot attools.py:149, also mentioned by antonwolfy as left to the author). A cheap pre-flight (e.g.parent.mkdir+ open-for-append check beforerun_probe) would avoid paying for a run that cannot be saved. Pre-existing behaviour; the PR adds the clean message but not the early check. OK as a follow-up.
Test evidence (Linux, uv run)
ruff check dokimi tests: clean.test_catalog_command.py test_skill_selection.py test_tool_preference.py test_tools_preference_items_out.py: 147 passed.
Verdict from my side: ready; the --out-json pre-flight can be a follow-up.
…ON, and an empty --out-json load_groups iterates groups only when it is a list, so a null value is reported by the schema check. The groups file read also catches RecursionError and ValueError. _save_result catches ValueError next to OSError and reports a schema rejection as a rejected artifact. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 53e5e33. Both fixes from my last review hold: "groups": null and --out-json "" now exit with a message. Two things are worth fixing before merge: the same deep-nesting input that was fixed for --groups still ends in a traceback in the manifest readers, and the new tool_preference artifact rejected branch has no test.
What I ran, on a Linux review host at 53e5e33:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). The changed-line coverage check passed.- Reproductions for the code findings, offline.
- Mutation runs: dropping the
ArtifactValidationErrorbranch in_save_resultleavestest_tool_preference.pyandtest_tools_preference_items_out.pygreen (77 passed). Reverting thegroups: null, deep-nesting and huge-int fixes makes the new parametrized cases fail.
@napetrov's and @exai-bot's question on checking --out-json before the paid run still waits for the author. The command now exits with a message, but the results are still lost when the path is unwritable.
Checked, no issues
--groups: about 50 inputs (nested wrong types, BOM, UTF-16, empty file, NaN, a directory, a missing path, a 5000-character path, 6000-digit ints,[and{"a":nested deeply) all exit with--groups <path>: ....--out-json: an empty path, a directory, a file as parent and.all exit 1 withError: cannot write <path>: ....--outintrigger-set: three formats against 19 odd paths (directory, file as parent, empty, symlink loop,/dev/null/x, 300-character name) give a one-line message, never a traceback.- Re-indented loop:
git diff -wshows only the new_manifest_taskscall in the loop header. - Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
napetrov
left a comment
There was a problem hiding this comment.
Delta review of 5d1c627f..53e5e33b.
New commit 53e5e33 (null groups, deep/huge JSON in --groups, empty --out-json, schema rejection reported separately from an unwritable path): reads correct; ArtifactValidationError is caught before the broader ValueError, so the two messages stay distinct. Nothing new from me, no commits pushed.
Still open (unchanged, follow-up OK): --out-json is only checked after the paid probe run.
Evidence: ruff check dokimi tests clean; test_tool_preference.py test_tools_preference_items_out.py 77 passed; CI 20/20 green at 53e5e33.
…with a message A deeply nested skills.yaml or suites.json raised RecursionError and a task name longer than the filesystem allows raised OSError from is_task_dir. Both now produce the invalid-manifest error or a failed catalog entry. Also cover the tool_preference artifact rejected branch with a test. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 6c4295c. Both fixes from my last review hold: a deeply nested skills.yaml or suites.json now exits with an invalid ... at <path> message, and the tool_preference artifact rejected branch has a test that fails when the clause is removed. Two risks remain on the neighbouring catalog paths, which still end in a traceback. Both predate the PR, but the changelog says a bad manifest exits with a message.
What I ran, on a Linux review host at 6c4295c:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). The changed-line coverage check passed.- Reproductions for the code findings, offline.
- Mutation runs: reverting the
RecursionErrorcatch forskills.yaml, the one forsuites.json, and theOSErrorhandler aroundis_task_dirmakes exactly one new test fail each. Removing theexcept ArtifactValidationErrorclause in_save_resultmakes the newtoolstest fail withcannot write.
Not anchored inline (outside this diff):
- A malformed
SKILL.mdunder--catalogstill ends in a traceback incatalog trigger-set.discover_skills(catalog_root)atcommands/catalog.py:171runs outside anytry. With--catalogholdinga/SKILL.mdand a validb/SKILL.md, and--questions q.json=b --out o.json: bytes\xff\xfegiveUnicodeDecodeError: 'utf-8' codec can't decode byte 0xff; frontmattername: [or a YAML list givesValueError: Skill 'a' (...) is missing a 'description' in its frontmatter; frontmatter of[repeated 100000 times givesRecursionError;a/SKILL.mdas a directory givesIsADirectoryError. All exit 1 and write no--out.catalog scanreports the same inputs per row, so the two commands disagree. Suggestion: wrap the call intry/except (OSError, ValueError, RecursionError)and raiseSystemExit(f"--catalog {catalog_root}: {exc}"), with a parametrized test for a non-UTF-8 and a missing-descriptionSKILL.md. catalog scan --kind taskswithout asuites.jsonstill ends in a traceback on a task directory with noinstruction.md.discover_tasks(tasks_root)atexternal/catalog.py:273is unguarded, unlike the manifest branch below it. Withevaluation/harbor/tasks/t/task.tomlpresent, noinstruction.mdand nosuites.json,catalog scan --root <dir> --kind tasksexits withFileNotFoundError: Task has no instruction.md: .../tasks/t. The same task listed insuites.jsongives a failed row. Suggestion: catch(OSError, ValueError)arounddiscover_tasks, raiseValueError(f"cannot read tasks at {tasks_root}: {exc}")(whichcommands/catalog.pyalready turns into a message), and add a test with a task directory holding onlytask.toml.
@napetrov's and @exai-bot's question on checking --out-json before the paid run still waits for the author. The command exits with a message, but the results are still lost when the path is unwritable.
Checked, no issues
- Over-long task name: with the tasks directory present, a 300-character
track: executablename gives a failed entry (cannot inspect task directory ...: File name too long) in text and JSON, exit 1. A 255-character name is skipped, exit 0. - Deep nesting:
[repeated 200000 times inskills.yaml(--kind skillsandall) and insuites.json(--kind tasksandall) exits with the invalid-manifest message. - Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
…irectory without instruction.md catalog trigger-set now reports an unreadable or malformed SKILL.md under --catalog, and catalog scan --kind tasks without suites.json reports a task directory missing instruction.md, instead of a traceback. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 61cdeb2. Both fixes from my last review hold: catalog trigger-set exits with a message on a malformed SKILL.md, and catalog scan --kind tasks does the same on a task directory without instruction.md. A sibling path in catalog scan still ends in a traceback for the nested-frontmatter input the new code handles, and the new tests do not pin two of the five new exception types. There is also one wording nit.
What I ran, on a Linux review host at 61cdeb2:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). The changed-line coverage check passed.- Reproductions for the code findings, offline.
- Mutation runs: reverting either call site makes the matching new test fail. Dropping
RecursionErrorfrom the_discover_catalog_skillstuple, orOSError(aSKILL.mdthat is a directory), leaves the 23 tests intest_catalog_command.pygreen.
Not anchored inline:
- Nit: the task-directory error is prefixed
suites.json:when nosuites.jsonexists._scan_tasks(commands/catalog.py:105, outside this diff) wraps everyValueErrorfromread_task_manifestassuites.json: {exc}. The new_discover_tasks_checkedruns only in the branch where the manifest is absent. A task directory holding onlytask.tomlgivessuites.json: cannot read tasks at <root>/evaluation/harbor/tasks: Task has no instruction.md: <root>/evaluation/harbor/tasks/t. Suggestion: drop thesuites.json:prefix, since theinvalid suites.json at <path>messages already name the file.
@napetrov's and @exai-bot's question on checking --out-json before the paid run waits for the author.
Checked, no issues
trigger-set --catalog: aSKILL.mdthat is a directory, a symlink loop, mode 0, a list frontmatter and a 100000-deep frontmatter all exit 1 with--catalog <root>: ....read_task_manifestsuccess path: hoistingfoundout of the comprehension leaves a good task plus an empty skills directory printing the sameokrow.- Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
catalog scan --kind skills now lists a SKILL.md whose frontmatter nests too deeply for the YAML parser as an error row, instead of ending in a traceback. The strict frontmatter check used by the validator reports the same case as a frontmatter-parse error. Tests cover a SKILL.md that is a directory, one that is not UTF-8, and one with deeply nested frontmatter, for both scan and trigger-set. The task directory test also covers an unparsable task.toml, which raises ValueError. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
tools preference checks --out-json before the first model call, so a path that cannot be written (a file used as a parent directory, or a directory) exits 2 with a message instead of after a paid run. Missing parent directories are created, and the probe file is removed when the path did not exist before. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 263ccde. Both fixes from my last review hold: a deeply nested SKILL.md is now an error row in catalog scan, the new tests pin each added exception, and the suites.json: prefix is gone from the task-directory message. One thing is worth fixing before merge: the new --out-json pre-flight in tools preference does not match what the real save does.
What I ran, on a Linux review host at 263ccde:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). The changed-line coverage check passed.- Reproductions for the code finding, offline.
- Mutation runs: dropping
RecursionErrorfromexternal/catalog.py, fromexternal/validate.pyor fromcommands/catalog.pymakes exactly one new test fail each.
Checked, no issues
- Prefix removal: no test or doc still expects
suites.json:in front of the task-directory error. - Deep
SKILL.md:catalog scan --kind skillsonname:followed by 100000[prints anERROR:row plus afrontmatter-parsefinding, exits 1, and the other skills still scan. - Pre-flight order:
--dry-runand a missing--modelexit before the check, and a missing target file is removed again. - Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
The out-json check now creates and removes a temp file beside the path the save writes to, instead of opening the target itself. A read-only existing file in a writable directory is accepted, a dangling symlink creates nothing at its target, and a symlink into a missing directory is refused before any probe call. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
The --out-json probe now matches the save in the cases I tried, and make ci-local is green. One thing is worth fixing before merge: the tests do not pin the new probe.
What I ran, on a Linux review host at ad114ec:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). 8,264 tests; changed-line coverage 97%.- Reproductions for the probe against the real save, offline.
- Mutation runs: 8 mutants of
_check_out_json_writable; 3 are caught (no_write_target, nois_dircheck, nomkdir) and 5 pass the full suite (no probe, probingtarget.parent,is_dirontarget, noValueErrorin theexcept, and a changed temp prefix). The first three are in the inline comment; theValueErrorone needs an embedded NUL byte in the path, which a shell cannot pass, and the prefix one is cosmetic.
Checked, no issues
- Probe against save: a read-only file in a writable directory, a symlink to one, a dangling symlink, a symlink into a missing directory, a symlink loop, a directory target and an empty path give the same accept or reject in the check and in
save_artifact. A dangling symlink leaves no file at its target, and no.tmpfile is left behind. - Call order:
_check_out_json_writableruns after--dry-runand the--modelcheck and before the first probe call. - Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
…argets Three tests cover a writable file in a read-only directory, a symlink whose target sits in a read-only directory, and a symlink to a directory. Each refuses with exit 2 before any probe call. The read-only cases skip on Windows and when the process can write through chmod 0555 (root). Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
test_tool_preference.py passed the 1000-line growth cap, so the out-json pre-flight tests move to test_tool_preference_out_json.py. The tests are unchanged apart from the moved location and an unused import. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 41466dd. The three tests from my last review are in, and they kill the probe mutants I named (no probe, target.parent, no is_dir on the resolved path). One nit inline: the probe's temp name is shorter than the save's, so a very long file name passes the pre-flight and the save then fails after the paid calls.
What I ran, on a Linux review host at 41466dd:
make ci-local: 29 passed, 0 failed, 7 skipped (docker, network and other-Python legs). 8,267 tests; changed-line coverage 97%.- Reproduction for the code finding, offline.
- Mutation runs: of 7 mutants of
_check_out_json_writable, 6 are caught (no probe,target.parent, nomkdir, nois_dircheck,open(..., 'ab')probe, no_write_target). DroppingValueErrorfrom theexceptpasses the suite; it needs an embedded NUL byte in the path, which a shell cannot pass, so I did not raise it.
Checked, no issues
- Probe against save: a read-only file, a symlink into a read-only directory, a dangling symlink, a symlink loop (as file and as parent), a trailing slash, an empty path and a
0555directory give the same accept or reject in the check and insave_artifact, and the probe leaves no file behind. - Call order: the check runs after
--dry-runand the--modelcheck, and before the first model call. - Repo rules: the changelog fragment and the growth baseline (16 to 15) pass; no schema or docs pages are touched.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
I pushed a commit per nit, up to 8f04440, which fix the nit from my review:
8f04440:dokimi/dokimi/commands/tools.py:129: the probe now creates<name>.<pid>.<thread id>.tmp, the save's temp name, and a new test pins a 240-character file name to exit 2.
make ci-local at 8f04440: 29 passed, 0 failed, 7 skipped.
GitHub CI has not run on these commits: the branch conflicts with main, and Actions runs no workflow on a conflicting PR. CI will check them once main is merged in.
Not approving until CI passes at this head.
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com> # Conflicts: # dokimi/scripts/growth_baseline.json
The manifest branch (_skill_entry) caught only OSError and ValueError around load_skill, so a deeply nested frontmatter ended catalog scan in a RecursionError traceback; the no-manifest branch already caught it. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
`data.get("suites") or []` turned {}, 0, false and "" into an empty list
before the shape check, so such a manifest scanned as 0 declared, rc 0.
Only null or a missing key now means empty, as in skills.yaml.
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. Both new commits are right: a SKILL.md that is too deeply nested and is listed in skills.yaml is now an error row, and suites.json rejects {}, 0 and {"tasks": {}} while null or a missing key still reads as empty. No new findings. @xaleryb left one choice to you: whether to turn RecursionError into ValueError once, in load_skill.
What I ran, on Linux at 3d29187:
make ci-local: 26 passed, 2 failed, 7 skipped; 8,722 tests; the changed-line coverage check passed. The two failures are the gitleaks install and scan steps, which could not download the gitleaks binary (curl: (22) ... 504); not caused by this PR.- Mutation runs on the new tests: dropping
RecursionErrorfrom_skill_entry, restoringor []forsuitesor fortasks, and removing eitherNoneto[]mapping each make at least one new test fail.
Checked, no issues
- Delta:
catalog scan --kind skillson askills.yamlthat lists aSKILL.mdwith[repeated 100000 times in the frontmatter prints anERROR:row, exits 1, and the other skills still scan. - Repo rules: the changelog fragment is present, the growth baseline entry (16 to 15) passes, and no schema is touched.
…talog-skills Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>


Summary
tools preference,catalog trigger-setandcatalog scanread a groups file and skill manifests and write--outand--items-outwithout handling OS or decode errors, so a bad file or path ends in a traceback.Changes
Each affected read or write now reports the path and reason in a one-line message and exits. One commit per command.
Risk
Error paths only.
Testing
make ci-local(offline), Python 3.13: everything passes exceptmypy(1 error) and the 10 tests that also fail onmain(process, sandbox, container probes; macOS). Load-flaky tests pass alone.Checklist
pytestpasses (apart from the 10 pre-existing failures)ruff,mypy, and schema checks pass (mypy: the one pre-existing error).github/.licenserc.yaml)